步骤 1:基本起点

从哪里开始使用 CMake?这一步将介绍 CMake 的一些基本语法、命令和变量。在介绍这些概念时,我们将完成三个练习并创建一个简单的 CMake 项目。

本步骤中的每个练习都将从一些背景信息开始。然后,将提供目标和有用的资源列表。Files to Edit 部分中的每个文件都位于 Step1 目录中,并包含一个或多个 TODO 注释。每个 TODO 代表要更改或添加的一两行代码。这些 TODO 应按数字顺序完成,首先完成 TODO 1,然后完成 TODO 2 等。Getting Started 部分将提供一些有用的提示并指导您完成练习。然后,Build and Run 部分将逐步介绍如何构建和测试练习。最后,在每个练习的末尾将讨论预期的解决方案。

另请注意,教程中的每个步骤都建立在下一个步骤的基础上。因此,例如,Step2 的起始代码是 Step1 的完整解决方案。

练习 1 - 构建基本项目

最基本的 CMake 项目是从单个源代码文件构建的可执行文件。对于这样的简单项目,仅需要一个包含三个命令的 CMakeLists.txt 文件。

注意:虽然 CMake 支持大写、小写和混合大小写的命令,但首选小写命令,本教程将始终使用小写命令。

任何项目的顶级 CMakeLists.txt 必须首先使用 cmake_minimum_required() 命令指定 CMake 的最低版本。这将建立策略设置并确保以下 CMake 函数与兼容版本的 CMake 一起运行。

要启动项目,我们使用 project() 命令设置项目名称。此调用对于每个项目都是必需的,并且应在 cmake_minimum_required() 之后立即调用。正如我们稍后将看到的那样,此命令还可用于指定其他项目级信息,例如语言或版本号。

最后,add_executable() 命令告诉 CMake 使用指定的源代码文件创建可执行文件。

目标

了解如何创建简单的 CMake 项目。

有用的资源

要编辑的文件

  • CMakeLists.txt

入门

tutorial.cxx 的源代码位于 Help/guide/tutorial/Step1 目录中,可用于计算数字的平方根。本步骤中无需编辑此文件。

在同一目录中有一个 CMakeLists.txt 文件,您需要完成此文件。从 TODO 1 开始,依次完成 TODO 3

构建和运行

完成 TODO 1TODO 3 之后,我们就可以构建和运行项目了!首先,运行 cmake 可执行文件或 cmake-gui 来配置项目,然后使用您选择的构建工具构建项目。

例如,从命令行,我们可以导航到 CMake 源代码树的 Help/guide/tutorial 目录,并创建一个构建目录

mkdir Step1_build

接下来,导航到该构建目录,并运行 cmake 来配置项目并生成本地构建系统

cd Step1_build
cmake ../Step1

然后调用该构建系统来实际编译/链接项目

cmake --build .

对于多配置生成器(例如 Visual Studio),首先导航到相应的子目录,例如

cd Debug

最后,尝试使用新构建的 Tutorial

Tutorial 4294967296
Tutorial 10
Tutorial

注意:根据 shell 的不同,正确的语法可能是 Tutorial./Tutorial.\Tutorial。为简便起见,练习将始终使用 Tutorial

解决方案

如上所述,我们只需要一个三行 CMakeLists.txt 即可启动和运行。第一行是使用 cmake_minimum_required() 设置 CMake 版本,如下所示

TODO 1:单击以显示/隐藏答案
TODO 1:CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

创建基本项目的下一步是使用 project() 命令,如下所示,以设置项目名称

TODO 2:单击以显示/隐藏答案
TODO 2:CMakeLists.txt
project(Tutorial)

创建基本项目所需的最后一个命令是 add_executable()。我们将其调用如下

TODO 3:单击以显示/隐藏答案
TODO 3:CMakeLists.txt
add_executable(Tutorial tutorial.cxx)

练习 2 - 指定 C++ 标准

CMake 有一些特殊的变量,这些变量要么是在幕后创建的,要么在项目代码设置后对 CMake 具有意义。许多这些变量以 CMAKE_ 开头。在为项目创建变量时,请避免使用此命名约定。这两个特殊的用户可设置变量是 CMAKE_CXX_STANDARDCMAKE_CXX_STANDARD_REQUIRED。它们可以一起使用来指定构建项目所需的 C++ 标准。

目标

添加一项需要 C++11 的功能。

有用的资源

要编辑的文件

  • CMakeLists.txt

  • tutorial.cxx

入门

继续编辑 Step1 目录中的文件。从 TODO 4 开始,完成到 TODO 6

首先,通过添加一项需要 C++11 的功能来编辑 tutorial.cxx。然后更新 CMakeLists.txt 以要求 C++11。

构建和运行

让我们再次构建项目。由于我们已经创建了构建目录并为练习 1 运行了 CMake,因此我们可以跳过构建步骤

cd Step1_build
cmake --build .

现在我们可以尝试使用新构建的 Tutorial,使用与之前相同的命令。

Tutorial 4294967296
Tutorial 10
Tutorial

解决方案

我们首先通过在 tutorial.cxx 中用 std::stod 替换 atof,将一些 C++11 特性添加到我们的项目中。这看起来如下所示。

TODO 4:单击以显示/隐藏答案
TODO 4:tutorial.cxx
  const double inputValue = std::stod(argv[1]);

要完成 TODO 5,只需删除 #include <cstdlib>

我们需要在 CMake 代码中明确说明它应该使用正确的标志。在 CMake 中启用对特定 C++ 标准的支持的一种方法是使用 CMAKE_CXX_STANDARD 变量。对于本教程,将 CMAKE_CXX_STANDARD 变量在 CMakeLists.txt 文件中设置为 11,并将 CMAKE_CXX_STANDARD_REQUIRED 设置为 True。确保在调用 add_executable() 之前添加 CMAKE_CXX_STANDARD 声明。

TODO 6:单击以显示/隐藏答案
TODO 6:CMakeLists.txt
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

练习 3 - 添加版本号和配置的头文件

有时将 CMakelists.txt 文件中定义的变量在源代码中也可用会很有用。在这种情况下,我们想打印项目版本。

实现这一点的一种方法是使用配置的头文件。我们创建一个包含一个或多个变量的输入文件以供替换。这些变量具有特殊语法,看起来像 @VAR@。然后,我们使用 configure_file() 命令将输入文件复制到给定的输出文件,并将这些变量替换为 CMakelists.txt 文件中 VAR 的当前值。

虽然我们可以直接在源代码中编辑版本,但使用此功能是首选,因为它创建了单一的事实来源并避免了重复。

目标

定义并报告项目的版本号。

有用资源

要编辑的文件

  • CMakeLists.txt

  • tutorial.cxx

入门

继续编辑 Step1 中的文件。从 TODO 7 开始,完成到 TODO 12。在本练习中,我们首先在 CMakeLists.txt 中添加项目版本号。在同一文件中,使用 configure_file() 将给定的输入文件复制到输出文件,并替换输入文件内容中的一些变量值。

接下来,创建一个输入头文件 TutorialConfig.h.in 定义版本号,该版本号将接受从 configure_file() 传递的变量。

最后,更新 tutorial.cxx 以打印其版本号。

构建和运行

让我们再次构建我们的项目。与之前一样,我们已经创建了一个构建目录并运行了 CMake,因此我们可以跳过构建步骤。

cd Step1_build
cmake --build .

验证在没有参数的情况下运行可执行文件时是否报告版本号。

解决方案

在本练习中,我们通过打印版本号来改进我们的可执行文件。虽然我们可以专门在源代码中执行此操作,但使用 CMakeLists.txt 使我们能够为版本号维护单一数据源。

首先,我们修改 CMakeLists.txt 文件以使用 project() 命令来设置项目名称和版本号。当调用 project() 命令时,CMake 会在幕后定义 Tutorial_VERSION_MAJORTutorial_VERSION_MINOR

TODO 7:单击以显示/隐藏答案
TODO 7:CMakeLists.txt
project(Tutorial VERSION 1.0)

然后我们使用 configure_file() 将输入文件复制到指定 CMake 变量替换后的输出文件。

TODO 8:单击以显示/隐藏答案
TODO 8:CMakeLists.txt
configure_file(TutorialConfig.h.in TutorialConfig.h)

由于配置后的文件将被写入项目二进制目录,因此我们必须将该目录添加到包含文件搜索路径列表中。

**注意:**在本教程中,我们将项目构建和项目二进制目录互换使用。这些是相同的,并不意味着引用 bin/ 目录。

我们使用 target_include_directories() 指定可执行目标应该在哪里查找包含文件。

TODO 9:单击以显示/隐藏答案
TODO 9:CMakeLists.txt
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

TutorialConfig.h.in 是要配置的输入头文件。当从我们的 CMakeLists.txt 中调用 configure_file() 时,@Tutorial_VERSION_MAJOR@@Tutorial_VERSION_MINOR@ 的值将被替换为项目中 TutorialConfig.h 中的相应版本号。

TODO 10:单击以显示/隐藏答案
TODO 10:TutorialConfig.h.in
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

接下来,我们需要修改 tutorial.cxx 以包含配置后的头文件 TutorialConfig.h

TODO 11:单击以显示/隐藏答案
TODO 11:tutorial.cxx
#include "TutorialConfig.h"

最后,我们通过更新 tutorial.cxx 如下打印出可执行文件名称和版本号。

TODO 12:单击以显示/隐藏答案
TODO 12 : tutorial.cxx
  if (argc < 2) {
    // report version
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
              << Tutorial_VERSION_MINOR << std::endl;
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }